package dd.lo.chapter8;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

/**
 * 练习5示例代码
 * Create your own simple paint program
 * 包含直线、矩形、圆形、椭圆绘制，以及文字输入（光标未做）
 */
public class Exercise5 extends Application {
    private enum Mode {
        LINE("Line"),
        RECTANGLE("Rectangle"),
        CIRCLE("Circle"),
        ELLIPSE("Ellipse"),
        TEXT("Text"),
        ;

        private final String name;

        public String getName() {
            return name;
        }

        Mode(String name) {
            this.name = name;
        }
    }

    static class Shape {
        final Mode mode;
        List<Point> points;
        Point tl;
        Point br;
        double w;
        double h;
        String text;

        public Shape(Mode mode, double x, double y) {
            this.mode = mode;
            points = new ArrayList<>();
            tl = new Point(x, y);
            text = "";
        }
    }

    private Mode mode = Mode.LINE;

    private Mat bg;

    private Mat curImg;

    private boolean drawing = false;

    private Shape curShape;
    private Deque<Shape> shapes = new LinkedList<>();

    private ImageView imageView;

    private Button lineModeBtn;
    private Button rectModeBtn;
    private Button circleModeBtn;
    private Button ellipseModeBtn;
    private Button textModeBtn;
    private Button undoBtn;

    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        int w = 800, h = 600;
        //创建一张图片
        bg = new Mat(600, 800, CvType.CV_8UC3);
        bg.setTo(new Scalar(255, 255, 255));
        imageView = new ImageView();
        imageView.setFitWidth(w);
        imageView.setFitHeight(h);
        imageView.setOnMouseMoved(e -> onMouseMove(e.getX(), e.getY()));
        imageView.setOnMouseClicked(e -> onMouseClicked(e.getX(), e.getY(), e.getButton()));
        undoBtn = new Button("Undo");
        undoBtn.setOnMouseClicked(e -> {
            if (!shapes.isEmpty()) {
                shapes.pop();
            }
            drawAll();
            display(curImg);
        });
        Button exitBtn = new Button("Exit");
        exitBtn.setOnMouseClicked(e -> Platform.exit());
        FlowPane rightAlignFlow = new FlowPane(Orientation.HORIZONTAL);
        rightAlignFlow.setAlignment(Pos.BASELINE_RIGHT);
        rightAlignFlow.getChildren().addAll(undoBtn, exitBtn);
        rightAlignFlow.setBorder(new Border(new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2))));
        lineModeBtn = new Button("Line");
        lineModeBtn.setOnMouseClicked(e -> changeMode(Mode.LINE));
        rectModeBtn = new Button("Rectangle");
        rectModeBtn.setOnMouseClicked(e -> changeMode(Mode.RECTANGLE));
        circleModeBtn = new Button("Circle");
        circleModeBtn.setOnMouseClicked(e -> changeMode(Mode.CIRCLE));
        ellipseModeBtn = new Button("Ellipse");
        ellipseModeBtn.setOnMouseClicked(e -> changeMode(Mode.ELLIPSE));
        textModeBtn = new Button("Text");
        textModeBtn.setOnMouseClicked(e -> changeMode(Mode.TEXT));
        FlowPane btmFlow = new FlowPane(lineModeBtn, rectModeBtn, circleModeBtn, ellipseModeBtn, textModeBtn, rightAlignFlow);
        btmFlow.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(1))));
        BorderPane root = new BorderPane();
        root.setCenter(imageView);
        root.setBottom(btmFlow);
        Scene scene = new Scene(root, w, h + 40);
        scene.setOnKeyPressed(e -> {
            if (drawing && curShape.mode == Mode.TEXT) {
                if (KeyCode.BACK_SPACE == e.getCode() && curShape.text.length() > 0) {
                    curShape.text = curShape.text.substring(0, curShape.text.length() - 1);
                } else {
                    curShape.text += e.getText();
                }
                Mat copy = curImg.clone();
                drawShape(copy, curShape);
                display(copy);
            }
        });
        stage.setScene(scene);
        stage.setTitle("Example4-Origin");
        stage.setResizable(false);
        drawAll();
        display(curImg);
        stage.show();
    }

    private void onMouseClicked(double x, double y, MouseButton mBtn) {
        if (mBtn == MouseButton.SECONDARY) {
            drawing = false;
            drawAll();
            display(curImg);
            return;
        }
        if (!drawing) {
            curShape = new Shape(mode, x, y);
        } else {
            curShape.w = x - curShape.tl.x;
            curShape.h = y - curShape.tl.y;
            shapes.push(curShape);
        }
        drawing = !drawing;
        if (!drawing) {
            drawAll();
            display(curImg);
        }
    }

    private void onMouseMove(double x, double y) {
        if (drawing && curShape.mode != Mode.TEXT) {
            Mat copy = curImg.clone();
            curShape.w = x - curShape.tl.x;
            curShape.h = y - curShape.tl.y;
            drawShape(copy, curShape);
            display(copy);
        }
    }

    private void changeMode(Mode mode) {
        this.mode = mode;
        drawAll();
        display(curImg);
    }

    private void drawShape(Mat img, Shape shape) {
        if (shape.mode == Mode.LINE) {
            Imgproc.line(img, shape.tl, new Point(shape.tl.x + shape.w, shape.tl.y + shape.h), new Scalar(0, 0, 0), 1, Imgproc.LINE_8);
        } else if (shape.mode == Mode.RECTANGLE) {
            Rect rect = new Rect((int) shape.tl.x, (int) shape.tl.y, (int) shape.w, (int) shape.h);
            if (shape.w < 0 && shape.h >= 0) {
                rect = new Rect((int) (shape.tl.x + shape.w), (int) shape.tl.y, Math.abs((int) shape.w), (int) shape.h);
            } else if (shape.w >= 0 && shape.h < 0) {
                rect = new Rect((int) shape.tl.x, (int) (shape.tl.y + shape.h), (int) shape.w, Math.abs((int) shape.h));
            } else if (shape.w < 0 && shape.h < 0) {
                rect = new Rect((int) (shape.tl.x + shape.w), (int) (shape.tl.y + shape.h), Math.abs((int) shape.w), Math.abs((int) shape.h));
            }
            Imgproc.rectangle(img, rect, new Scalar(0, 0, 0), 1, Imgproc.LINE_8);
        } else if (shape.mode == Mode.CIRCLE) {
            Imgproc.circle(img, new Point(shape.tl.x, shape.tl.y), (int) Math.sqrt(shape.w * shape.w + shape.h * shape.h), new Scalar(0, 0, 0), 1, Imgproc.LINE_8);
        } else if (shape.mode == Mode.ELLIPSE) {
            RotatedRect rr = new RotatedRect(new Point(shape.tl.x + shape.w / 2, shape.tl.y + shape.h / 2), new Size(Math.abs(shape.w), Math.abs(shape.h)), 0);
            Imgproc.ellipse(img, rr, new Scalar(0, 0, 0), 1, Imgproc.LINE_8);
        } else if (shape.mode == Mode.TEXT) {
            Imgproc.putText(img, shape.text, shape.tl, Imgproc.FONT_HERSHEY_SIMPLEX, 0.6, new Scalar(0, 0, 0), 1, Imgproc.LINE_8);
        }
    }

    private void drawAll() {
        curImg = bg.clone();
        Imgproc.putText(curImg, String.format("Mode: %s", mode.getName()), new Point(0, 15), Imgproc.FONT_HERSHEY_SIMPLEX, 0.6, new Scalar(0, 0, 0), 1, Imgproc.LINE_8);
        for (Shape shape : shapes) {
            drawShape(curImg, shape);
        }
    }

    private void display(Mat img) {
        MatOfByte buf = new MatOfByte();
        Imgcodecs.imencode(".jpeg", img, buf);
        Platform.runLater(() -> imageView.setImage(new Image(new ByteArrayInputStream(buf.toArray()))));
    }
}
